import threading, Queue, random, thread,time
from math import *
# TODO Need to place walls and overlay sensors
class graphics_engine(object):
	def __init__(self,height=480,width=640):
		self.tk_root=None
		self.main_canvas=None
		self.height=height
		self.width=width
		self.action_queue=Queue.Queue()
		self.mainloop_thread=threading.Thread(target=self.__event_loop_wrap)
		self.agent_graphics_id={} # This is the graphics handle that uniquely identified the agent
		self.root_created=thread.allocate_lock() # This is to ensure that root is created before start event loop returns
	def start_event_loop(self):
		"""This creates the graphics thread, and only exists when tk_root is valid"""
		self.root_created.acquire()
		self.mainloop_thread.start()
		self.root_created.acquire()
		self.root_created.release()
	def __action_loop(self,**args):
		delay_time=10
		lots_of_commands=2
		if self.action_queue.qsize() >= lots_of_commands: # If we are behind in processing, speed it up
			delay_time=1
		try:
			cur_action=self.action_queue.get(block=False)
			cur_action()
		except Queue.Empty:
			pass
		self.tk_root.after(delay_time,self.__action_loop)
	def __event_loop_wrap(self):
		import Tkinter
		self.tk_root=Tkinter.Tk()
		# Custom Event Bindings (these don't change)
		#self.tk_root.bind('<<stop_event_loop>>',self.__stop_event_loop)
		# Create canvas and run loop
		self.main_canvas=Tkinter.Canvas(self.tk_root,width=self.width,height=self.height)
		self.main_canvas.pack()
		self.__action_loop()
		self.tk_root.bind('q',lambda x: self.__stop_event_loop())
		self.root_created.release()
		self.tk_root.mainloop()
	# The following functions are thread safe event callers
	# These rebind the functions with the provided arguments, and then throw the event to allow passing arguments to events (this is a great hack BTW, lambdas FTW)
	# The naming convention for the wrapper (event caller) is NAME, the event function's name is __NAME, and the event is <<NAME>>
	def update_pos(self,agent_num,pos,heading):
		"""This will update the position and heading of an agent graphically, if it doesn't exist it will be created"""
		self.action_queue.put(lambda : self.__update_pos(agent_num,pos,heading))
	def create_walls(self,walls,width):
		"""This will graphically create a wall from the provided list specified by 2 points located in a tuple in the format (x_0,y_0,x_1,y_1)"""
		self.action_queue.put(lambda : self.__create_walls(walls,width))
	def show_graph(self, graph):
		self.action_queue.put(lambda : self.__show_graph(graph))
	def show_path(self, path):
		self.action_queue.put(lambda : self.__show_path(path))
	def remove_agent(self,agent_num):
		"""This will remove the agent from the graphics list if it exists"""
		pass
	def stop_event_loop(self):
		"""This will stop the event loop from running and join in the thread"""
		self.tk_root.event_generate('<<stop_event_loop>>',when='tail')
		self.mainloop_thread.join() # This removes the extra thread after the mainloop quits
	# The following functions must be called by custom events (above)
	def __update_pos(self,agent_num,pos,heading):
		if self.agent_graphics_id.has_key(agent_num): # If it already exists
			self.main_canvas.coords(self.agent_graphics_id[agent_num],graphics_engine.make_pos_segment(pos,heading))
		else: # If it doesn't exist
			self.agent_graphics_id[agent_num]=self.main_canvas.create_line(graphics_engine.make_pos_segment(pos,heading),arrow='last')	
	def __remove_agent(self,agent_num):
		if self.agent_graphics_id.has_key(agent_num): # If it exists
			self.main_canvas.delete(self.agent_graphics_id[agent_num])
			del self.agent_graphics_id[agent_num]
	def __create_walls(self,walls,width):
		map(lambda x: self.main_canvas.create_line(x,width=width),walls)
	def __show_graph(self, graph):
		map(lambda x: self.main_canvas.create_rectangle(x[0]-2,x[1]-2,x[0]+2,x[1]+2, outline='green'), graph)
		for x in graph:
			map(lambda y: self.main_canvas.create_line(x[0], x[1], y[0], y[1], fill='blue'), graph[x])
	def __show_path(self, path):
		map(lambda x: self.main_canvas.create_rectangle(x[0]-2,x[1]-2,x[0]+2,x[1]+2, outline='yellow'), path)
		for i in range(path.__len__()-1):
			self.main_canvas.create_line(path[i][0], path[i][1], path[i+1][0], path[i+1][1], fill='red')
	def __stop_event_loop(self):
		"""This stops the mainloop, it must be called FROM mainloop, thus can be triggered by an event"""
		self.tk_root.destroy()
	# These functions provide commonly used minor functionality
	def random_pos(self):
		return [self.width*random.random(),self.height*random.random()]
	# Draws a vector that indicates the direction the agent is facing
	@staticmethod
	def make_pos_segment(pos,heading):
		return (pos[0],pos[1],pos[0]+cos(heading),pos[1]+sin(heading))
